#include "config.h"

#include "ms2lstat.h"

ecm_int32	stat_iatime = -1;
ecm_uint32	stat_mid = 0;
ecm_uint32	stat_media_rank = 0;

ecm_hashtable_t	hash_media;
static ecm_ptrs_t	*ptrs_media;
static ecm_uint32	n_total_accessed;

static LIST_HEAD(sorted_pop_medias);
static LIST_HEAD(sorted_size_medias);

static media_t *
create_media(const char *uri)
{
	media_t	*media;

	media = (media_t *)ecm_malloc(sizeof(media_t));
	media->mid = ecm_ptrs_add(ptrs_media, media);
	ecm_hashtable_str_insert(hash_media, media, uri);
	media->uri = ecm_strdup(uri);
	media->length = 0;
	media->size = 0;
	media->n_accessed = 0;

	INIT_LIST_HEAD(&media->list);
	INIT_LIST_HEAD(&media->list_size);
	list_add_tail(&media->list, &sorted_pop_medias);
	list_add(&media->list_size, &sorted_size_medias);

	return media;
}

ecm_uint32
get_media_id(const char *uri)
{
	media_t	*media;

	media = (media_t *)ecm_hashtable_str_get(hash_media, uri);
	if (media == NULL) {
		media = create_media(uri);
	}
	return media->mid;
}

media_t *
get_media(ecm_uint32 mid)
{
	return (media_t *)ecm_ptrs_get(ptrs_media, mid);
}

ecm_uint32
get_media_rank(ecm_uint32 mid)
{
	media_t	*media = (media_t *)ecm_ptrs_get(ptrs_media, mid);
	media_t	*media_ranked;
	struct list_head	*lp;
	ecm_uint32	rank = 1;

	list_for_each (lp, &sorted_pop_medias) {
		media_ranked = list_entry(lp, media_t, list);
		if (media_ranked == media)
			return rank;
		rank++;
	}
	return rank;
}

static void
sort_media_size(media_t *media)
{
	media_t	*media_list;
	struct list_head	*lp;

	if (media->list.next == &sorted_size_medias)
		return;

	list_del(&media->list_size);
	list_for_each (lp, &sorted_size_medias) {
		media_list = list_entry(lp, media_t, list_size);
		if (media_list->size > media->size) {
			list_add_tail(&media->list_size, &media_list->list_size);
			return;
		}
	}

	list_add_tail(&media->list_size, &sorted_size_medias);
}

ECMBOOL
update_media_info(ecm_uint32 mid, ecm_uint64 length, ecm_uint64 size)
{
	media_t	*media;

	media = (media_t *)ecm_ptrs_get(ptrs_media, mid);
	if (media == NULL) {
		ERR("media is not found");
		return ECM_FALSE;
	}

	if (media->length > 0) {
		if (media->length != length) {
			if (!suppress_display_error)
				ERR("%u: media length changed: old: %I64u, new: %I64u", mid, media->length, length);
			media->length = length;
		}
	}
	else
		media->length = length;

	if (media->size > 0) {
		if (media->size != size) {
			if (!suppress_display_error)
				ERR("%u: media size changed: old: %I64u, new: %I64u", mid, media->size, size);
			media->size = size;
		}
	}
	else
		media->size = size;

	sort_media_size(media);

	return ECM_TRUE;
}

static void
sort_media_popularity(media_t *media)
{
	media_t	*media_higher;
	struct list_head	*lp;

	if (media->list.prev == &sorted_pop_medias)
		return;

	list_del(&media->list);
	for (lp = media->list.prev; lp != &sorted_pop_medias; lp = lp->prev) {
		media_higher = list_entry(lp, media_t, list);
		if (media_higher->n_accessed >= media->n_accessed) {
			list_add(&media->list, &media_higher->list);
			return;
		}
	}

	list_add(&media->list, &sorted_pop_medias);
}

void
incr_media_access(session_t *session)
{
	ecm_uint32	mid;
	media_t	*media;

	if (stat_iatime >= 0) {
		ecm_int32	iatime;

		iatime = get_session_interarrival(session);
		if (iatime != stat_iatime)
			return;
	}

	mid = session->mid;
	media = (media_t *)ecm_ptrs_get(ptrs_media, mid);
	ECMASSERT(media != NULL);

	media->n_accessed++;
	n_total_accessed++;
	sort_media_popularity(media);
}

static void
report_media_infos(void)
{
	media_t	*media;
	ecm_uint32	i;
	FILE	*fp_media = setup_logging("media");

	ecm_fprintf(fp_media, "# mid length size n_accessed ratio uri\n");
	for (i = 0; i < ptrs_media->len; i++) {
		media = (media_t *)ptrs_media->pdata[i];
		ecm_fprintf(fp_media, "%6u %8llu %8llu %6u %lf %s\n",
			    i + 1, media->length, media->size, media->n_accessed,
			    ((ecm_double)media->n_accessed) / n_total_accessed,
			    media->uri);
	}
	ecm_fclose(fp_media);
}

static void
report_media_popularity(void)
{
	struct list_head	*lp;
	FILE	*fp_pop = setup_logging("popularity");
	ecm_uint32	rank = 1;
	media_t	*media;

	ecm_fprintf(fp_pop, "# rank mid prob n_accessed normalized_ia\n");
	list_for_each (lp, &sorted_pop_medias) {
		media = list_entry(lp, media_t, list);
		if (media->n_accessed > 0) {
			ecm_double	norm_ia = 0;
			if (media->length > 0) {
				ecm_double	peak_interarrival = get_media_peak_interarrival(media->mid);
				if (peak_interarrival > 0)
					norm_ia =  peak_interarrival / media->length;
			}
			ecm_fprintf(fp_pop, "%5u %5u %9.8lf %u %lf\n",
				    rank, media->mid, (1.0 * media->n_accessed) / n_total_accessed,
				    media->n_accessed,
				    norm_ia);
		}
		rank++;
	}
	
	ecm_fclose(fp_pop);
}

static void
report_media_size_dist(void)
{
	struct list_head	*lp;
	FILE	*fp_size = setup_logging("mediasize");
	ecm_uint32	total_accu_count = 0, accu_count = 0;
	media_t	*media;

	ecm_fprintf(fp_size, "# size accumulated.count\n");
	list_for_each (lp, &sorted_size_medias) {
		media_t	*media_next;

		media = list_entry(lp, media_t, list_size);
		if (media->size == 0)
			continue;
		if (lp->next != &sorted_size_medias)
			media_next = list_entry(lp->next, media_t, list_size);
		else
			media_next = NULL;

		if (media_next == NULL || media_next->size != media->size) {
			ECMASSERT(media_next == NULL || media_next->size > media->size);
			total_accu_count += accu_count;
			accu_count = 0;
			ecm_fprintf(fp_size, "%5u %6u\n", media->size, total_accu_count);
		}
		else {
			accu_count++;
		}
	}
	
	ecm_fclose(fp_size);
}

void
report_media_result(FILE *fp)
{
	ecm_fprintf(fp, "Total Number of Accessed Medias:    %u\n", ecm_hashtable_getsize(hash_media));

	report_media_infos();
	report_media_popularity();
	report_media_size_dist();
}

ECMBOOL
init_media(void)
{
	hash_media = ecm_hashtable_create(256);
	ptrs_media = ecm_ptrs_new(256);

	return ECM_TRUE;
}
